package cn.yunyichina.openapi.proxy.http;

import cn.yunyichina.openapi.proxy.config.HttpServerConfig;
import cn.yunyichina.openapi.proxy.entity.Response;
import cn.yunyichina.openapi.proxy.utils.AuthorizeUtil;
import cn.yunyichina.openapi.proxy.utils.HttpServerUtil;
import cn.yunyichina.openapi.proxy.utils.ServiceUtil;
import cn.yunyichina.utils.convert.JsonUtils;
import cn.yunyichina.utils.log.Log;
import cn.yunyichina.utils.log.constant.LogParamType;
import cn.yunyichina.utils.log.constant.LogScene;
import cn.yunyichina.utils.security.Base64Method;
import com.alibaba.dubbo.rpc.service.GenericService;
import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger logger = LoggerFactory.getLogger(HttpServerHandler.class);

    private long beginTime = 0;
    private long endTime = 0;
    private String printAllStr = "";


    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.info("************* Notify http request start *************");
        beginTime = System.currentTimeMillis();
        HttpRequest request = (HttpRequest) msg;

        // 1.判断解码是否失败
        if (request.decoderResult().isFailure()) {
            String responseStr = JsonUtils.toJsonString(new Response("4", "解码失败"));
            writeResponse(request, ctx, true, responseStr);
            return;
        }

        //解释http报文头的编码
        Charset charset = CharsetUtil.UTF_8;
        String httpContentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
        if (!StringUtil.isNullOrEmpty(httpContentType)) {
            String contentType = httpContentType.toLowerCase();
            if (contentType.indexOf("charset") > -1 && contentType.substring(contentType.indexOf("charset")).contains("gbk")) {
                charset = Charset.forName("GBK");
            }
        }

        // 2.打印请求报文
        URI uri = new URI(request.uri());
        if (uri.getPath().equals("/favicon.ico")) {
            return;
        }
        printRequestBuffer(request, msg,charset);

        /* 请求类型  2-回调 分析出调用的方法 */
        if (uri.getPath().length() == 0) {
            String responseStr = JsonUtils.toJsonString(new Response("5", "url路径长度为0"));
            writeResponse(request, ctx, true, responseStr);
            return;
        }
        String urlPath = uri.getPath().substring(1);
        String[] urlSplits = urlPath.toLowerCase().split("/");
        if (urlSplits.length < 4) {
            String responseStr = JsonUtils.toJsonString(new Response("5", "url路径错误"));
            writeResponse(request, ctx, true, responseStr);
            return;
        }
        String callMethod = HttpServerUtil.getMethod(urlSplits);

        // 4.获取用户Http Get/Post 传入的参数
        String params = null;
        if (request.method().equals(HttpMethod.GET)) { // http请求Get方式

            //--------------------------------------GETT方式处理[开始]--------------------------------------------------
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri(), charset);
            Map<String, List<String>> httpGetParams = queryStringDecoder.parameters();
            Map<String, String> httpParams;
            try {
                httpParams = HttpServerUtil.convertMaplistToMap(httpGetParams);
            } catch (Exception ex) {
                writeResponse(request, ctx, true, "GET解码失败:" + ex.getMessage());
                return;
            }
            params = JsonUtils.toJsonString(httpParams);
            //--------------------------------------GETT方式处理[结束]--------------------------------------------------

        } else if (request.method().equals(HttpMethod.POST)) { // http请求Post方式(Content-Type: application/x-www-form-urlencoded; charset=utf-8)

            //--------------------------------------POST方式处理[开始]--------------------------------------------------
            if (httpContentType != null && (httpContentType.toLowerCase().indexOf("application/x-www-form-urlencoded") > -1)) {
                HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
                HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request, charset);
                try {
                    if (decoder.hasNext()) {
                        List<InterfaceHttpData> bodyHttpDatas = decoder.getBodyHttpDatas();
                        Map<String, String> temp = new HashMap<>();
                        for (InterfaceHttpData data : bodyHttpDatas) {
                            if (data instanceof Attribute) {
                                Attribute attribute = (Attribute) data;
                                temp.put(attribute.getName(), attribute.getValue());
                            }
                        }
                        params = JsonUtils.toJsonString(temp);
                    }
                } catch (RuntimeException ex) {
                    String responseStr = JsonUtils.toJsonString(new Response("6", "application/x-www-form-urlencoded POST解码失败!"));
                    writeResponse(request, ctx, true, responseStr);
                    return;
                }
            } else {//http raw 类型
                HttpContent httpContent = (HttpContent) msg;
                ByteBuf content = httpContent.content();
                params = content.toString(charset);
            }
            //----------------------------------------POST方式处理[结束]-----------------------------------------------

        } else {
            String responseStr = JsonUtils.toJsonString(new Response("6", "接口只支持HTTP/HTTPS GET/POST协议"));
            writeResponse(request, ctx, true, responseStr);
            return;
        }

        //5.权限的校验,回调授权直接返回成功
        Map<String, String> validateParams = new HashMap<>();
        validateParams.put("method", callMethod);
        Response response = AuthorizeUtil.validate(validateParams);
        if (!"0".equals(response.getResultCode())) {
            String responseStr = JsonUtils.toJsonString(response);
            writeResponse(request, ctx, true, responseStr);
            return;
        }

        // 6.调用dubbo服务
        String callDubboReseponse = callDubboService(params, callMethod);

        // 7.写回给客户端
        writeResponse(request, ctx, false, callDubboReseponse,callMethod);

        logger.info("************* Notify http request end *************");
    }

    /**
     * 调用远程服务
     *
     * @param params
     * @param callMethod
     * @return
     * @throws Exception
     */
    private String callDubboService(String params, String callMethod) throws Exception {
        String result;
        String serviceDefine = HttpServerUtil.getServiceDefine(callMethod);
        if (HttpServerUtil.isEmpty(serviceDefine)) {
            result = JsonUtils.toJsonString(new Response("9", "接口method参数出错，请检查提交的参数"));
            return result;
        }

        if (!ServiceUtil.getDicServices().containsKey(serviceDefine)) {
            //重新加载
            ServiceUtil.reLoad(serviceDefine);
            if (!ServiceUtil.getDicServices().containsKey(serviceDefine)) {
                result = JsonUtils.toJsonString(new Response("10", "没有该方法的服务，请注册到数据库"));
                return result;
            }
        }

        GenericService service = (GenericService) ServiceUtil.getDicServices().get(serviceDefine).getService();
        try {
            HashMap<Object, Object> rpcParams = new HashMap<>();
            rpcParams.put("data", Base64Method.encryptNormalBase64(params));
            rpcParams.put("method", callMethod);

            Object dubboResponseObj = service.$invoke("callFromExternal", new String[]{"java.util.Map"}, new Object[]{rpcParams});
            Response dubboResponse = JSON.parseObject(JSON.toJSONString(dubboResponseObj), Response.class);
            result = dubboResponse.getResult();
        } catch (Exception e) {
            logger.error("调用dubbo异常:", e);
            result = JsonUtils.toJsonString(new Response("6", "调用远程方法失败,异常" + e.getMessage()));
        }

        if (result == null) {
            result = JsonUtils.toJsonString(new Response("6", "调用远程方法失败"));
        }

        return result;
    }

    private void writeResponse(HttpRequest request, final ChannelHandlerContext ctx, boolean isCloseHttp, String responseBody) throws URISyntaxException {
        writeResponse( request,  ctx,  isCloseHttp,  responseBody,"");
    }

    /**
     * 将结果返回给用户
     *
     * @param request
     * @param ctx
     * @throws URISyntaxException
     */
    private void writeResponse(HttpRequest request, final ChannelHandlerContext ctx, boolean isCloseHttp, String responseBody,String callMethod) throws URISyntaxException {

        /* 1.当不是主动关闭http通道的时候检测 */
        if (!isCloseHttp) {
            if (request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)) {
                isCloseHttp = true;
            } else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
                isCloseHttp = true;
            }
        }

        /* 2.当closehttp为flase的时候 */
        boolean keepAlive = false;
        if (!isCloseHttp) {
            keepAlive = HttpUtil.isKeepAlive(request);
        }

        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, request.decoderResult()
                .isSuccess() ? HttpResponseStatus.OK : HttpResponseStatus.BAD_REQUEST, Unpooled.copiedBuffer(
                responseBody.toString(), CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

        if (keepAlive && HttpServerConfig.getConfig().getSupportkeepalive()) {
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        } else {
            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        }

        endTime = System.currentTimeMillis();
        printAllStr = printAllStr + ";  调用结果返回:" + responseBody;
//        logger.info(printAllStr);
        Log.i(LogScene.NOTIFY,callMethod, LogParamType.NORMAL,endTime-beginTime,printAllStr);

        final ChannelFuture future = ctx.writeAndFlush(response);
        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture channelFuture) {
                assert future == channelFuture;
                ctx.close();
                logger.info("Operation completed.");
            }
        });
    }

    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        logger.info("Request Is Exception:" + cause.getMessage());
        ctx.close();
    }

    /**
     * 打印请求信息
     *
     * @param request
     */
    private void printRequestBuffer(HttpRequest request, Object msg,Charset charset) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("\r\n####################### BEGIN REQUEST #######################\r\n");
        buffer.append("HOST: ").append(request.headers().get(HttpHeaderNames.HOST, "unknown")).append("\r\n");
        buffer.append("PROTOCOL_VERSION: ").append(request.protocolVersion()).append("\r\n");
        buffer.append("REQUEST_METHOD: ").append(request.method()).append("\r\n");
        buffer.append("REQUEST_URI: ").append(request.uri()).append("\r\n");
        printAllStr = "请求URL:" + request.uri() + "||";
        HttpHeaders headers = request.headers();
        if ((headers != null) && !headers.isEmpty()) {
            for (Map.Entry<String, String> header : headers) {
                buffer.append("HEADER: ").append(header.getKey()).append(" = ").append(header.getValue()).append("\r\n");
            }
        }

        if (request.method().equals(HttpMethod.POST)) {
            HttpContent httpContent = (HttpContent) msg;
            ByteBuf content = httpContent.content();
            String postData = content.toString(charset);
            printAllStr = printAllStr + postData;
            buffer.append(postData).append("\r\n");
        }
        buffer.append("####################### END REQUEST #######################");
        logger.info(buffer.toString());
    }

}
